package com.taotao.tools.spi;

import com.taotao.tools.spi.exception.ExtensionClassNotFoundException;
import com.taotao.tools.spi.resource.Disposable;
import com.taotao.tools.spi.utils.ExtensionUtils;
import com.taotao.tools.spi.utils.Holder;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

/**
 * 扩展加载器
 *
 * @param <T>
 */
@Slf4j
public class ExtensionLoader<T> {

    /**
     * 默认扩展目录
     */
    private static final String EXT_DIRECTORY = "META-INF/tt-spi/";

    /**
     * 名称分隔符
     */
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");


    /**
     * ExtensionLoader 缓存
     */
    private static final ConcurrentMap<Class<?>, Object> extensionLoaderInstances = new ConcurrentHashMap<>(64);


    //    private final List<ExtensionPostProcessor> extensionPostProcessors;
    protected final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

    /**
     * extName -> Class
     */
    protected final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    /**
     * extName -> instance
     */
    protected final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();


    private final ConcurrentMap<Class<?>, Object> extensionInstances = new ConcurrentHashMap<>(64);

    protected final Class<?> type;
    protected final ExtensionFactory extensionFactory;
    protected final String extDir;

    private final AtomicBoolean destroyed = new AtomicBoolean();

    protected ExtensionLoader(Class<?> type, ExtensionFactory extensionFactory, String extDir) {
        this.type = type;
        this.extensionFactory = extensionFactory;
        this.extDir = extDir;
    }

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type, ExtensionFactory extensionFactory) {
        return getExtensionLoader(type, extensionFactory, EXT_DIRECTORY);
    }

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type, ExtensionFactory extensionFactory, String extDir) {
        ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) extensionLoaderInstances.get(type);
        if (null != extensionLoader) {
            return extensionLoader;
        }

        synchronized (ExtensionLoader.class) {
            if (null != extensionLoader) {
                return extensionLoader;
            }

            extensionLoader = new ExtensionLoader<>(type, extensionFactory, extDir);
            extensionLoaderInstances.put(type, extensionLoader);
        }


        return extensionLoader;
    }


    protected Map<String, Class<?>> loadExtensionClasses() {
        Map<String, Class<?>> extClassMap = new HashMap<>();
        ExtensionUtils.loadFile(extClassMap, extDir, type);
        return extClassMap;
    }


    /**
     * 获取扩展类
     *
     * @param name
     * @return
     */
    private Class<?> getExtensionClass(String name) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        if (name == null) {
            throw new IllegalArgumentException("Extension name == null");
        }
        return getExtensionClasses().get(name);
    }

    private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw new ExtensionClassNotFoundException(name);
        }
        try {
            T instance = (T) extensionInstances.get(clazz);
            if (instance == null) {
                instance = (T) createExtensionInstance(clazz,name);
                extensionInstances.putIfAbsent(clazz, instance);
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException(
                    "Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(),
                    t);
        }
    }

    private T postProcessAfterInitialization(T instance, String name) {
        return instance;
    }

    private T postProcessBeforeInitialization(T instance, String name) {
        return instance;
    }

    /**
     * 扩展实例注入逻辑
     *
     * @param instance
     */
    private void injectExtension(T instance) {


    }


    /**
     * 创建扩展实例
     *
     * @param type
     * @return
     */
    private Object createExtensionInstance(Class<?> type,String name) {
        return extensionFactory.createAndInitExtension(type,name);
    }


    public void destroy() {
        if (!destroyed.compareAndSet(false, true)) {
            return;
        }
        // destroy raw extension instance
        extensionInstances.forEach((type, instance) -> {
            if (instance instanceof Disposable) {
                Disposable disposable = (Disposable) instance;
                try {
                    disposable.destroy();
                } catch (Exception e) {
                    log.error("Error destroying extension " + disposable, e);
                }
            }
        });
        extensionInstances.clear();

        // destroy wrapped extension instance
        for (Holder<Object> holder : cachedInstances.values()) {
            Object wrappedInstance = holder.get();
            if (wrappedInstance instanceof Disposable) {
                Disposable disposable = (Disposable) wrappedInstance;
                try {
                    disposable.destroy();
                } catch (Exception e) {
                    log.error("Error destroying extension " + disposable, e);
                }
            }
        }
        cachedInstances.clear();
    }

}
